/*
  Copyright (C) 2011, Kyungjoo Kim
  
  This file is part of UHM (Unassembled HyperMatrix) sparse direct solver.
  
  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
  
  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  Also add information on how to contact you by electronic and paper mail.
  
  
  Kyungjoo Kim
  iamkyungjoo@gmail.com
*/
#ifndef UHM_NET_HXX
#define UHM_NET_HXX


namespace uhm {

  typedef class Net_*     Net;
  typedef class Mesh_*    Mesh;
  typedef class Node_*    Node;
  typedef class Element_* Element;
  typedef class Matrix_*  Matrix;

  bool net_valid(Net net);

  // ----------------------------------------------------------------
  // ** Net class
  class Net_ : public Object_< int > {
  protected:
    // Two cases possible
    // 1. central input --> connectivity comes from processor
    //   - need to partition the mesh into children meshes.
    // 2. distributive input --> respect partition user already made
    
    // self does not need buid tree but need partition
    int locker;
    Mesh_ self;
    std::vector< Mesh_ > children;
    void _init(int id);
    
  public:
    Net_();
    Net_(int id);
    virtual ~Net_();

    virtual bool disp() { return this->disp(stdout); }
    virtual bool disp(FILE *stream);

    bool operator<(const Net_ &b) const;
    
    Mesh get_self();
    Mesh get_child(int loc);
    
    void reset_children();
    void reset_children(int n_children);

    int  get_n_children();
        
    virtual void lock();
    virtual void unlock();

    int  is_locked();

    void decompose_local_with_free(int method);
    void solve_1_local(int method);
    void solve_2_local(int method);
    void solve_local(int method);
    void check_local(int method);

    void decompose_local_with_free();
    void solve_1_local();
    void solve_2_local();
    void solve_local();
    void check_local();

    friend bool net_valid(Net net);
  };

  // ----------------------------------------------------------------
  // ** Definition
  inline Net_::Net_()       { this->_init(0); }
  inline Net_::Net_(int id) { this->_init(id); }
  inline Net_::~Net_() { }

  inline void Net_::_init(int id) {
    this->id         = id;
    this->cookie     = UHM_NET_COOKIE;
    this->locker     = 0;
  }
  inline bool Net_::operator<(const Net_ &b) const { 
    return (this->id < b.id); 
  }

  inline Mesh Net_::get_self() { return &(this->self); }
  inline Mesh Net_::get_child(int loc) { return &(this->children.at(loc)); }
  inline void Net_::reset_children() { this->children.clear(); }
  inline void Net_::reset_children(int n_children) {
    this->children.clear(); 
    this->children.reserve(n_children);
  }
  inline int  Net_::get_n_children() { return this->children.size(); }
  inline void Net_::lock() {
    UHM_ERROR(!this->is_locked(), ">> Net is already locked");

    Mesh m    = this->get_self();
    if (!m->is_locked()) m->lock();
    int decomposition =  m->get_decomposition_method();

    Element e = m->get_root();
    e->set_nodes_kind(UHM_NODE_KIND_BOUNDARY);

    int n     = e->get_n_children(); 
    this->reset_children( n );
    
    Mesh_ in;
    this->children.assign( n, in );
    
    for (int i=0;i<n;++i) {
      Mesh c = this->get_child(i);
      c->set_decomposition_method( decomposition );
      c->insert_element( e->get_child(i) );
      c->adjust_element_numbering();
    }

    this->locker = true;

    // remove layer of leaves
    e->set_nodes_kind(UHM_NODE_KIND_DEFAULT);
    m->remove_leaves();
    m->unlock();
    m->lock();
  }
  inline void Net_::unlock() { this->locker = false; }
  inline int  Net_::is_locked() { return this->locker; }

  inline bool Net_::disp(FILE *stream) {

    fprintf(stream, ":: Net :: mesh self\n");
    this->get_self()->disp(stream);
    for (int i=0;i<this->get_n_children();++i) { 
      fprintf(stream, ":: Net :: mesh child %5d \n", i);
      this->get_child(i)->disp(stream);
    }

    return true;
  }

  inline void Net_::decompose_local_with_free() {
    this->decompose_local_with_free(this->get_self()->get_decomposition_method());
  }
  inline void Net_::decompose_local_with_free(int method) {
#pragma omp parallel
    {
#pragma omp single nowait
      {
        for (int i=0;i<this->get_n_children();++i) {

#pragma omp task firstprivate(i)
          {
            this->get_child(i)->decompose_with_free(method);
          }
        }
      }
    }
  }

  inline void Net_::solve_1_local() {
    this->solve_1_local(this->get_self()->get_decomposition_method());
  }
  inline void Net_::solve_1_local(int method) {
#pragma omp parallel
    {
#pragma omp single nowait
      {
        for (int i=0;i<this->get_n_children();++i) {

#pragma omp task firstprivate(i)
          {
            this->get_child(i)->solve_1(method);
          }
        }
      }
    }
  }
  inline void Net_::solve_2_local() {
    this->solve_2_local(this->get_self()->get_decomposition_method());
  }
  inline void Net_::solve_2_local(int method) {
#pragma omp parallel
    {
#pragma omp single nowait
      {
        for (int i=0;i<this->get_n_children();++i) {

#pragma omp task firstprivate(i)
          {
            this->get_child(i)->solve_2(method);
          }
        }
      }
    }
  }
  inline void Net_::solve_local() {
    this->solve_local(this->get_self()->get_decomposition_method());
  }
  inline void Net_::solve_local(int method) {
#pragma omp parallel
    {
#pragma omp single nowait
      {
        for (int i=0;i<this->get_n_children();++i) {

#pragma omp task firstprivate(i)
          {
            this->get_child(i)->solve(method);
          }
        }
      }
    }
  }

  inline void Net_::check_local() {
    this->check_local(this->get_self()->get_decomposition_method());
  }
  inline void Net_::check_local(int method) {
    for (int i=0;i<this->get_n_children();++i) 
      this->get_child(i)->check(method);
  }

  inline bool net_valid(Net e) { 
    return (e && e->cookie == UHM_NET_COOKIE); 
  }

}

#endif
